#include "CSignal.h"
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/wait.h>
#include "CLogger.h"
#include "CProcessAttribute.h"

signal_t CSignal::signals[] = {
	// signo		signame			handler
	{ SIGHUP,		"SIGHUP",		signalHandler},
	{ SIGINT,		"SIGINT",		signalHandler},
	{ SIGTERM,		"SIGTERM",		signalHandler},
	{ SIGCHLD,		"SIGCHLD",		signalHandler},
	{ SIGQUIT,		"SIGQUIT",		signalHandler},
	{ SIGIO,		"SIGIO",		signalHandler},
	{ SIGSYS,		"SIGSYS, SIG_IGN",		nullptr},

	//...日后根据需要再继续增加
	{ 0,			nullptr,		nullptr}
};

//初始化信号，用于注册信号处理程序
int CSignal::initSignals()
{
	signal_t* sig;			//指向signals的指针
	struct sigaction sa;

	for (sig = signals; sig->signo != 0; sig++)		//将signo == 0作为一个标记，因为信号的编号都不为0
	{
		//我们注意，现在要把一堆信息往变量sa对应的结构里弄 ......
		memset(&sa, 0, sizeof(struct sigaction));

		if (sig->handler)	// 如果信号处理函数不为空，表示要定义自己的信号处理函数
		{
			sa.sa_sigaction = sig->handler;	// sa_sigaction：指定信号处理函数，sa_sigaction也是函数指针，是struct sigaction中的一个成员（函数成员指针）
			sa.sa_flags = SA_SIGINFO;		// sa_flags：int，指定信号的一些选项，设置了该标记（SA_SIGINFO），就表示信号附带的参数可以被传递到信号处理函数中
											// 如果想让sa.sa_sigaction指定的信号处理函数生效，就得把sa_flags设置为SA_SIGINFO
		}
		else
		{
			sa.sa_handler = SIG_IGN;		// sa_handler：这个标记SIG_IGN给到sa_handler成员，表示忽略信号的处理程序，否则操作系统缺省的信号处理程序很可能把这个进程杀死
											// 其实sa_handler和sa_sigaction都是一个函数指针用来表示信号处理程序。只不过这两个函数指针的参数不一样，sa_sigaction带的参数多，信息量大，
											// 而sa_handler带的参数少，信息量少；如果想用sa_sigaction，就需要把sa_flags设置为SA_SIGINFO
		}

		sigemptyset(&sa.sa_mask);			// sa_mask是个信号集（描述信号的集合），用于表示要阻塞的信号，sigemptyset函数将信号集中所有信号清0，不准备阻塞任何信号
											// 举例：如果在处理SIGUSR1时，不希望收到SIGUSR2信号，那就可以用sigaddset(&sa.sa_mask, SIGUSR2); 阻塞SIGUSR2信号
											// sigaction(SIGUSR1, &sa, NULL)，设置SIGUSR1的信号处理函数和处理该信号时阻塞的信号集，此处只有SIGUSR2

		// 设置信号处理动作（信号处理函数）
		if (sigaction(sig->signo, &sa, NULL) == -1)		//参数1：要操作的信号
														//参数2：主要就是那个信号处理函数以及要执行信号处理函数时要屏蔽的信号
														//参数3：返回以往的信号处理方式
		{
			CLogger::logErrorCore(LOG_EMERG, errno, "sigaction(%s) failed", sig->signame);
			return -1;
		}
		else {
			//成功时打印日志
			CLogger::logErrorCore(LOG_INFO, errno, "sigaction(%s) succeed!", sig->signame);
		}
	}
	return 0;
}

//信号处理函数
//siginfo: 这个系统定义的结构中包含了信号产生原因的有关信息
void CSignal::signalHandler(int signo, siginfo_t* siginfo, void* context)
{
	signal_t* sig;
	char* lpAction;	//一个字符串，用于记录一个动作字符串并且向日志文件中写

	for (sig = signals; sig->signo != 0; sig++)
	{
		//找到对应信号，即可处理
		if (sig->signo == signo)
		{
			break;
		}
	}
	lpAction = (char*)"";	//目前没什么动作
	if (CProcessAttribute::m_snProcessType == PROCESS_MASTER)
	{
		switch (signo)
		{
		case SIGCHLD:	//子进程退出
			CProcessAttribute::m_snReap = 1;
			break;

		default:
			break;
		}
	}
	else if (CProcessAttribute::m_snProcessType == PROCESS_WORKER)
	{
		// worker进程的处理方式
	}

	//记录一些日志信息
	if (siginfo && siginfo->si_pid)	// si_pid = sending process ID(发送该信号的进程id)
	{
		CLogger::logErrorCore(LOG_NOTICE, 0, "signal %d (%s) received from %P%s", signo, sig->signame, siginfo->si_pid, lpAction);
	}
	else {
		CLogger::logErrorCore(LOG_NOTICE, 0, "signal %d (%s) received %s", signo, sig->signame, lpAction);	//没有发送该信号的进程ID，所以不显示发送该信号的进程id
	}

	//子进程状态有变化，通常是意外退出
	if (signo == SIGCHLD)
	{
		getProcessStatus();	//获取子进程的状态
	}
	
}

//获取子进程的结束状态，防止单独kill子进程时子进程变成僵尸进程
void CSignal::getProcessStatus()
{
	pid_t pid;
	int status;
	int err;
	int one = 0;	//标记信号正常处理过一次

	for (; ;)
	{
		//获取子进程的终止状态，这样子进程就不会成为僵死进程了
		//返回值>0表示成功，返回的是子进程进程ID
		pid = waitpid(-1, &status, WNOHANG);	//第一个参数为-1，表示等待任何子进程
												//第二个参数：保存子进程的状态信息
												//第三个参数：提供额外选项，WNOHANG表示不要阻塞，让waitpid立即返回
		if (pid == 0)	//子进程没有结束，会返回这个值
		{
			return;
		}

		if (pid == -1)
		{
			err = errno;	
			
			if (err == EINTR)	//被信号中断
			{
				continue;
			}

			if (err == ECHILD && one)	//没有子进程
			{
				return;
			}

			if (err == ECHILD)
			{
				CLogger::logErrorCore(LOG_INFO, err, "waitpid() failed!");
				return;
			}

			CLogger::logErrorCore(LOG_ALERT, err, "waitpid() failed!");
			return;
		}
		//走到这里，表示成功返回
		one = 1;
		if (WTERMSIG(status))
		{
			CLogger::logErrorCore(LOG_ALERT, 0, "pid = %P exited on signal %d!", pid, WTERMSIG(status));	//获取使子进程终止的信号编号
		}
		else {
			CLogger::logErrorCore(LOG_NOTICE, 0, "pid = %P exited with code %d!", pid, WEXITSTATUS(status));	//WEXITSTATUS()获取子进程传递给exit或者_exit参数的低八位
		}
	}
}
